package org.cloud.common.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.Charset;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.cloud.common.exception.SlothCloudException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * FTP上传下载工具类
 * 
 * @author wangyuan
 * @since 2018年9月17日
 */
public class FTPUtils {

    private static final Logger logger = LoggerFactory.getLogger(FTPUtils.class);

    private static String hostname = null;
    private static Integer port = null;
    private static String username = null;
    private static String password = null;

    public static FTPClient initFtpClient() {
        FTPClient ftpClient = null;
        try {
            logger.info("connecting...ftp服务器:{}:{}", hostname, port);

            ftpClient = new FTPClient();
            ftpClient.setControlEncoding("utf-8");
            ftpClient.setCharset(Charset.forName("UTF-8"));
            ftpClient.connect(hostname, port);
            ftpClient.login(username, password);
            int replyCode = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                logger.info("connecting failed...ftp服务器:{}:{}", hostname, port);
            }
            logger.info("connecting successful...ftp服务器:{}:{}", hostname, port);
        } catch (Exception e) {
            logger.error("连接FTP服务器时出现错误，原因是：", e);
            throw new SlothCloudException("连接FTP服务器时出现错误，无法继续执行ftp相关操作");
        }
        
        return ftpClient;
    }

    /**
     * 从本地上传文件至ftp服务器
     * @param pathName 文件目录
     * @param fileName 文件名称
     * @param originFileName 文件路径
     */
    public static void uploadFile(String pathName, String fileName, String originFileName) {
        FTPClient ftpClient = null;
        InputStream inputStream = null;
        try {
            logger.info("开始上传文件");
            ftpClient = initFtpClient();
            ftpClient.setBufferSize(1024 * 1024);
            ftpClient.enterLocalPassiveMode();
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            createDirecrotyIfNotExist(ftpClient, pathName);
            ftpClient.makeDirectory(pathName);
            ftpClient.changeWorkingDirectory(pathName);
            inputStream = new BufferedInputStream(new FileInputStream(new File(originFileName)));
            boolean uploadResult = ftpClient.storeFile(fileName, inputStream);
            
            if(uploadResult){
                logger.info("上传文件成功");
            } else {
                logger.info("上传文件失败");
            }
            ftpClient.logout();
        } catch (Exception e) {
            logger.error("上传文件失败", e);
            throw new SlothCloudException("上传文件失败");
        } finally {
            closeFtpClient(ftpClient);
            StreamUtils.close(inputStream);
        }
    }

    /**
     * 上传文件
     * 
     * @param pathName 文件所在ftp目录
     * @param fileName 文件名称
     * @param inputStream 文件流
     * @return
     */
    public static void uploadFile(String pathName, String fileName, InputStream inputStream) {
        FTPClient ftpClient = null;
        try {
            logger.info("开始上传文件");
            ftpClient = initFtpClient();
            ftpClient.setBufferSize(1024 * 1024);
            ftpClient.enterLocalPassiveMode(); //被动模式
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            createDirecrotyIfNotExist(ftpClient, pathName);
            ftpClient.makeDirectory(pathName);
            ftpClient.changeWorkingDirectory(pathName);
            boolean uploadResult = ftpClient.storeFile(fileName, inputStream);
            if(uploadResult){
                logger.info("上传文件成功");
            } else {
                logger.info("上传文件失败");
            }
            ftpClient.logout();
        } catch (Exception e) {
            logger.error("上传文件失败", e);
            throw new SlothCloudException("上传文件失败");
        } finally {
            closeFtpClient(ftpClient);
            StreamUtils.close(inputStream);
        }
    }

    /**
     * 创建目录（如果不存在）
     * 
     * @param remoteDir
     * @return
     * @throws IOException
     */
    private static boolean createDirecrotyIfNotExist(FTPClient ftpClient, String remoteDir) throws IOException {
        boolean success = true;
        String directory = remoteDir + "/";
        // 如果远程目录不存在，则递归创建远程服务器目录
        if (!directory.equalsIgnoreCase("/") && !changeWorkingDirectory(ftpClient, new String(directory))) {
            int start = 0;
            int end = 0;
            if (directory.startsWith("/")) {
                start = 1;
            } else {
                start = 0;
            }
            end = directory.indexOf("/", start);
            String path = "";
            String paths = "";
            while (true) {
                String subDirectory = new String(remoteDir.substring(start, end).getBytes("GBK"), "iso-8859-1");
                path = path + "/" + subDirectory;
                if (!existFile(ftpClient, path)) {
                    if (makeDirectory(ftpClient, subDirectory)) {
                        changeWorkingDirectory(ftpClient, subDirectory);
                    } else {
                        logger.warn("创建目录[{}]失败", subDirectory);
                        changeWorkingDirectory(ftpClient, subDirectory);
                    }
                } else {
                    changeWorkingDirectory(ftpClient, subDirectory);
                }

                paths = paths + "/" + subDirectory;
                start = end + 1;
                end = directory.indexOf("/", start);
                // 检查所有目录是否创建完毕
                if (end <= start) {
                    break;
                }
            }
        }
        return success;
    }

    /**
     * 变换FTP工作目录
     * 
     * @param directory
     * @return
     */
    private static boolean changeWorkingDirectory(FTPClient ftpClient, String directory) {
        boolean flag = false;
        try {
            flag = ftpClient.changeWorkingDirectory(directory);
            if (flag) {
                logger.info("进入文件夹{} 成功！", directory);
            } else {
                logger.info("进入文件夹{} 失败！开始创建文件夹", directory);
            }
        } catch (IOException e) {
            logger.error("变换FTP工作目录时出现错误，原因是：", e);
            throw new SlothCloudException("变换FTP工作目录出现错误");
        }
        return flag;
    }

    /**
     * 检测是否存在子文件
     * 
     * @param path
     * @return
     * @throws IOException
     */
    private static boolean existFile(FTPClient ftpClient, String path) throws IOException {
        boolean flag = false;
        FTPFile[] ftpFileArr = ftpClient.listFiles(path);
        if (ftpFileArr.length > 0) {
            flag = true;
        }
        return flag;
    }

    /**
     * 创建目录
     * 
     * @param dir
     * @return
     */
    private static boolean makeDirectory(FTPClient ftpClient, String dir) {
        boolean flag = true;
        try {
            flag = ftpClient.makeDirectory(dir);
            if (flag) {
                logger.info("创建文件夹{} 成功！", dir);
            } else {
                logger.info("创建文件夹{} 失败！", dir);
            }
        } catch (Exception e) {
            logger.error("创建FTP工作文件夹时出现错误，原因是：", e);
            throw new SlothCloudException("创建FTP工作文件夹出现错误");
        }

        return flag;
    }

    /**
     * 下载文件
     * 
     * @param pathName 远程文件目录
     * @param fileName 文件名
     * @return
     */
    public static ByteArrayOutputStream downloadFile(String pathName, String fileName) {
        FTPClient ftpClient = null;
        ByteArrayOutputStream out = null;
        try {
            logger.info("开始下载文件");
            ftpClient = initFtpClient();
            ftpClient.setBufferSize(1024 * 1024);
            ftpClient.enterLocalPassiveMode(); //被动模式
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.changeWorkingDirectory(pathName);
            FTPFile[] ftpFiles = ftpClient.listFiles();
            for (FTPFile file : ftpFiles) {
                if (fileName.equalsIgnoreCase(file.getName())) {
                    out = new ByteArrayOutputStream();
                    ftpClient.retrieveFile(file.getName(), out);
                    break;
                }
            }

            ftpClient.logout();
            logger.info("下载文件成功");
        } catch (Exception e) {
            logger.error("下载文件失败", e);
            throw new SlothCloudException("下载文件失败");
        } finally {
            closeFtpClient(ftpClient);
        }

        return out;
    }
    
    /**
     * 下载文件
     * @param pathName
     * @param fileName
     * @param out
     * @return
     */
    public static OutputStream downloadFile(String pathName, String fileName, OutputStream out) {
        FTPClient ftpClient = null;
        try {
            logger.info("开始下载文件");
            ftpClient = initFtpClient();
            ftpClient.setBufferSize(1024 * 1024);
            ftpClient.enterLocalPassiveMode(); //被动模式
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            ftpClient.changeWorkingDirectory(pathName);
            FTPFile[] ftpFiles = ftpClient.listFiles();
            for (FTPFile file : ftpFiles) {
                if (fileName.equalsIgnoreCase(file.getName())) {
                    ftpClient.retrieveFile(file.getName(), out);
                    break;
                }
            }

            ftpClient.logout();
            logger.info("下载文件成功");
        } catch (Exception e) {
            logger.error("下载文件失败", e);
            throw new SlothCloudException("下载文件失败");
        } finally {
            closeFtpClient(ftpClient);
        }

        return out;
    }
    
    /**
     * 下载文件
     * @param pathName 文件目录
     * @param fileName 文件名称
     * @param localPath 存储至本地文件路径
     */
    public static void downloadFile(String pathName, String fileName, String localPath) {
        downloadFile(pathName, fileName, localPath, null);
    }
    
    public static void downloadFile(String pathName, String fileName, String localPath, String fileNewName) {
        FTPClient ftpClient = null;
        OutputStream out = null;
        try {
            logger.info("开始下载文件");
            ftpClient = initFtpClient();
            ftpClient.setBufferSize(1024 * 1024);
            ftpClient.enterLocalPassiveMode();
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            FTPFile[] ftpFiles = ftpClient.listFiles(pathName);
            
            for (FTPFile file : ftpFiles) {
                if (fileName.equalsIgnoreCase(file.getName())) {
                    File localFile = new File(localPath + "/" + (StringUtils.isEmpty(fileNewName) ? file.getName() : fileNewName));
                    out = new FileOutputStream(localFile);
                    ftpClient.retrieveFile(file.getName(), out);
                    break;
                }
            }
            ftpClient.logout();
            logger.info("下载文件成功");
        } catch (Exception e) {
            logger.error("下载文件失败", e);
            throw new SlothCloudException("下载文件失败");
        } finally {
            closeFtpClient(ftpClient);
        }
    }

    /**
     * 删除文件
     * 
     * @param pathName 文件目录
     * @param fileName 文件名称
     * @return
     */
    public static void deleteFile(String pathName, String fileName) {
        FTPClient ftpClient = null;
        try {
            logger.info("开始删除文件");
            ftpClient = initFtpClient();
            ftpClient.enterLocalActiveMode();
            ftpClient.changeWorkingDirectory(pathName);
            int result = ftpClient.dele(fileName);
            logger.info("删除文件结果：{}", result);
            ftpClient.logout();
            logger.info("删除文件成功");
        } catch (Exception e) {
            logger.error("删除文件失败", e);
            throw new SlothCloudException("删除文件失败");
        } finally {
            closeFtpClient(ftpClient);
        }
    }

    /**
     * 改名FTP上的文件
     * @param srcName 原名称
     * @param targetName 新名称
     * @return
     */
    public static boolean renameFile(String srcName,String targetName){
        boolean flag = false;
        FTPClient ftpClient = null;
        try {
            logger.info("开始重命名文件");
            ftpClient = initFtpClient();
            ftpClient.enterLocalActiveMode();
            flag = ftpClient.rename(srcName,targetName);
            logger.info("重命名文件结果：{}", flag);
            ftpClient.logout();
            logger.info("重命名文件成功");
        } catch (Exception e) {
            logger.error("重命名文件失败", e);
            throw new SlothCloudException("重命名文件失败");
        } finally {
            closeFtpClient(ftpClient);
        }
        return flag;
    }

    /**
     * 复制文件
     * @param pathName 上传目录
     * @param fileUrl 文件url
     * @param fileName 文件名称
     */
    public static void copyFile(String pathName, String fileUrl, String fileName) {
        try{
            URL url = new URL(fileUrl);
            URLConnection urlConnection = url.openConnection();
            InputStream is = urlConnection.getInputStream();
            uploadFile(pathName,fileName,is);
        }catch (Exception ex){
            ex.printStackTrace();
            throw new SlothCloudException(ex.getMessage());
        }
    }

    private static void closeFtpClient(FTPClient ftpClient) {
        if (ftpClient.isConnected()) {
            try {
                ftpClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void setHostname(String hostname) {
        FTPUtils.hostname = hostname;
    }

    public static void setPort(int port) {
        FTPUtils.port = port;
    }

    public static void setUsername(String username) {
        FTPUtils.username = username;
    }

    public static void setPassword(String password) {
        FTPUtils.password = password;
    }

}
